package de.zalando.toga.generator.dimensions; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; public class ObjectDimension extends Dimension { private static class Prefix { final JsonNode value; final Prefix parent; final String key; Prefix(Prefix parent, String key, JsonNode value) { this.parent = parent; this.value = value; this.key = key; } // put the whole prefix into given collection ObjectNode addTo(ObjectNode node) { if (parent != null) { parent.addTo(node); } node.put(key, value); return node; } } private static class PrettyPrinter extends DefaultPrettyPrinter { public static final PrettyPrinter instance = new PrettyPrinter(); public PrettyPrinter() { _arrayIndenter = DefaultPrettyPrinter.Lf2SpacesIndenter.instance; } } private final List<Dimension> attributes = new ArrayList<>(); public ObjectDimension(String name, JsonNode node) { super(name); Iterator<Map.Entry<String, JsonNode>> iterator = node.fields(); while (iterator.hasNext()) { Map.Entry<String, JsonNode> entry = iterator.next(); attributes.add(Dimension.from(entry.getKey(), entry.getValue())); } } @Override public List<JsonNode> getValueDimensions() { ObjectMapper mapper = new ObjectMapper(); // empty object special case if (attributes.isEmpty()){ return singletonList(mapper.createObjectNode()); } // get value dimensions for each attribute Map<String, List<JsonNode>> dimensions = attributes.stream().collect(toMap(Dimension::getFieldName, Dimension::getValueDimensions)); // permutate //http://stackoverflow.com/questions/32131987/how-can-i-make-cartesian-product-with-java-8-streams return permute(dimensions).collect(toList()); } /** * Generate a json representation of this object. * @return a String containing a json array with all possible permutations of the given object. */ public String generateJson() { JsonFactory factory = new JsonFactory(); StringWriter stringWriter = new StringWriter(); ObjectMapper objectMapper = new ObjectMapper(); try { JsonGenerator generator = factory.createGenerator(stringWriter); ArrayNode arrayNode = objectMapper.createArrayNode(); getValueDimensions().stream().forEach(arrayNode::add); objectMapper.writeTree(generator, arrayNode); return toPrettyString(stringWriter.toString()); } catch (IOException e) { throw new RuntimeException("Error writing node json nodes.", e); } } private String toPrettyString(String json) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); Object temp = objectMapper.readValue(json, Object.class); return objectMapper.writer(PrettyPrinter.instance).writeValueAsString(temp); } private Stream<JsonNode> combine( Map<String, List<JsonNode>> map, List<String> keys, int offset, Prefix prefix) { String key = keys.get(offset); if (offset == map.size() - 1) return map.get(key).stream() .map(node -> new Prefix(prefix, key, node).addTo(new ObjectMapper().createObjectNode())); return map.get(key).stream() .flatMap(node -> combine(map, keys, offset + 1, new Prefix(prefix, key, node))); } private Stream<JsonNode> permute( Map<String, List<JsonNode>> map) { if (map.isEmpty()) return Stream.empty(); return combine(map, new ArrayList<>(map.keySet()), 0, null); } }